﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Diagnostics.Contracts;

    /// <summary>
    /// Defines extensions methods for the <see cref="IRandomGenerator"/> interface.
    /// </summary>
    internal static class IRandomGeneratorExtensions
    {
        /// <summary>
        /// Gets the next random integer value which is greater than zero and less than or equal to
        /// the specified maxmimum value.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <param name="max">The maximum random integer value to return.</param>
        /// <returns>A random integer value between zero and the specified maximum value.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// Thrown if the value passed to the <paramref name="max"/> parameter is less than one.
        /// </exception>
        static internal Int32 NextInteger(this IRandomGenerator instance, Int32 max)
        {
            Contract.Requires<ArgumentNullException>(instance != null);
            Contract.Requires<ArgumentOutOfRangeException>(max > 0);

            return (Int32)(max * instance.NextSingle());
        }

        /// <summary>
        /// Gets the next random integer between the specified minimum and maximum values.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <param name="min">The inclusive minimum value.</param>
        /// <param name="max">The inclusive maximum value.</param>
        /// <returns>A random integer between the specified minimum and maximum values.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// Thrown if the value passed to the <paramref name="min"/> parameter is less than zero,
        /// or if the value passed to the <paramref name="max"/> parameter is less than one.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// Thrown if the value passed to the <paramref name="max"/> parameter is less than the
        /// value passed to the <paramref name="min"/> parameter.
        /// </exception>
        static internal Int32 NextInteger(this IRandomGenerator instance, Int32 min, Int32 max)
        {
            Contract.Requires<ArgumentNullException>(instance != null);
            Contract.Requires<ArgumentOutOfRangeException>(min >= 0);
            Contract.Requires<ArgumentOutOfRangeException>(max > 1);
            Contract.Requires<ArgumentException>(min <= max);

            return (Int32)((max - min) * instance.NextSingle()) + min;
        }

        /// <summary>
        /// Gets the next random integer between the specified range of values.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <param name="range">A range representing the inclusive minimum and maximum values.</param>
        /// <returns>A random integer between the specified minumum and maximum values.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        static internal Int32 NextInteger(this IRandomGenerator instance, Interval range)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            return instance.NextInteger(range.X, range.Y);
        }

        /// <summary>
        /// Gets the next random single value which is greater than zero and less than or equal to
        /// the specified maxmimum value.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <param name="max">The maximum random single value to return.</param>
        /// <returns>A random single value between zero and the specified maximum value.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        static internal Single NextSingle(this IRandomGenerator instance, Single max)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            return max * instance.NextSingle();
        }

        /// <summary>
        /// Gets the next random single value between the specified minimum and maximum values.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <param name="min">The inclusive minimum value.</param>
        /// <param name="max">The inclusive maximum value.</param>
        /// <returns>A random single value between the specified minimum and maximum values.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        static internal Single NextSingle(this IRandomGenerator instance, Single min, Single max)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            return ((max - min) * instance.NextSingle()) + min;
        }

        /// <summary>
        /// Gets the next random single value between the specified range of values.
        /// </summary>
        /// <param name="instance">The exension instance.</param>
        /// <param name="range">A range representing the inclusive minimum and maximum values.</param>
        /// <returns>A random single value between the specified minimum and maximum values.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        static internal Single NextSingle(this IRandomGenerator instance, IntervalF range)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            return instance.NextSingle(range.X, range.Y);
        }

        /// <summary>
        /// Gets the next random colour value.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <returns>A random colour value.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        static internal Colour NextColour(this IRandomGenerator instance)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            return new Colour(instance.NextSingle(), instance.NextSingle(), instance.NextSingle());
        }

        /// <summary>
        /// Gets the next random angle value.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <returns>A random angle value.</returns>
        /// single
        static internal RadianAngle NextAngle(this IRandomGenerator instance)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            var rads = NextSingle(instance, Calculator.τ) - Calculator.π;

            return new RadianAngle(rads);
        }

        /// <summary>
        /// Gets the next random direction.
        /// </summary>
        /// <param name="instance">The extension instance.</param>
        /// <returns>A <see cref="Axis"/> facing in a random direction.</returns>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="instance"/> parameter is <c>null</c>.
        /// </exception>
        static internal Axis NextAxis(this IRandomGenerator instance)
        {
            Contract.Requires<ArgumentNullException>(instance != null);

            var radians = (Single)NextAngle(instance);

            var z = NextSingle(instance, -1f, 1f);

            var t = Calculator.SquareRoot(1f - (z * z));

            var x = Calculator.Cos(radians) * t;
            var y = Calculator.Sin(radians) * t;

            return new Axis(x, y, z);
        }
    }
}